<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="czVnE" id="czVnE"><span data-lake-id="ua3594ac6" id="ua3594ac6">典型回答</span></h1>
  <p data-lake-id="uf130bf71" id="uf130bf71"><br></p>
  <p data-lake-id="u54d87356" id="u54d87356"><span data-lake-id="ua44d5e21" id="ua44d5e21">深度分页问题是指在数据库查询中，当你尝试访问通过分页查询返回的结果集的后面部分（即深层页码）时遇到的性能问题。</span></p>
  <p data-lake-id="u2b868295" id="u2b868295"><span data-lake-id="uaa60c250" id="uaa60c250">​</span><br></p>
  <p data-lake-id="ud782500d" id="ud782500d"><span data-lake-id="u111c6462" id="u111c6462">假设你有一个包含数百万条记录的表，你想通过分页的方式来展示这些数据。当用户请求第10000页数据时，假设pageSize为10，那么最终就是LIMIT 99990,10 ，数据库必须先扫描过前99990条记录，才能返回第10000页的数据，这会导致显著的性能下降。</span></p>
  <p data-lake-id="u9bc0b01b" id="u9bc0b01b"><span data-lake-id="uc0f817e1" id="uc0f817e1">​</span><br></p>
  <blockquote data-lake-id="u021542a4" id="u021542a4">
   <p data-lake-id="u7372eb21" id="u7372eb21"><span data-lake-id="ucd6c3739" id="ucd6c3739">99991是起始ID = (页数 - 1) * 每页项目数 + 1 </span></p>
   <p data-lake-id="ue5289562" id="ue5289562"><span data-lake-id="u2dac006e" id="u2dac006e">对于第1页，起始ID将是1，结束ID将是10。对于第2页，起始ID将是11，结束ID将是20，以此类推。</span></p>
   <p data-lake-id="ueff1b693" id="ueff1b693"><span data-lake-id="u28c5327a" id="u28c5327a">对于第10000页：</span></p>
   <p data-lake-id="ub8a8d725" id="ub8a8d725"><span data-lake-id="u8603237e" id="u8603237e">起始ID = (10000 - 1) * 10 + 1 = 99991</span></p>
  </blockquote>
  <p data-lake-id="u927b7f9d" id="u927b7f9d"><br></p>
  <p data-lake-id="u9b6dcfd4" id="u9b6dcfd4"><span data-lake-id="uebc31460" id="uebc31460">深度分页的问题想要优化，有以下几个手段。</span></p>
  <p data-lake-id="u4e146067" id="u4e146067"><br></p>
  <h3 data-lake-id="ErnEB" id="ErnEB"><span data-lake-id="uf3401815" id="uf3401815">使用子查询和JOIN优化</span></h3>
  <p data-lake-id="ud5b918cd" id="ud5b918cd"><br></p>
  <p data-lake-id="u1b53d704" id="u1b53d704"><span data-lake-id="u93ac5743" id="u93ac5743">假如我们这样一条SQL：</span></p>
  <p data-lake-id="ue5b5d3eb" id="ue5b5d3eb"><span data-lake-id="ucb843d10" id="ucb843d10">​</span><br></p>
  <pre lang="java"><code>
SELECT c1, c2, cn... FROM table WHERE name = "Hollis" LIMIT 1000000,10
</code></pre>
  <p data-lake-id="u686274e4" id="u686274e4"><span data-lake-id="ue2b2de3c" id="ue2b2de3c">​</span><br></p>
  <p data-lake-id="u85013675" id="u85013675"><span data-lake-id="u33a451c3" id="u33a451c3">我们可以基于子查询进行优化，如以下SQL：</span></p>
  <p data-lake-id="u1a3cf244" id="u1a3cf244"><span data-lake-id="ub7e7eba8" id="ub7e7eba8">​</span><br></p>
  <pre lang="java"><code>
SELECT c1, c2, cn...
FROM table
INNER JOIN (
    SELECT id
    FROM table
    WHERE name = "Hollis"
    ORDER BY id
    LIMIT 1000000, 10
) AS subquery ON table.id = subquery.id

</code></pre>
  <p data-lake-id="u4cf6e1a2" id="u4cf6e1a2"><br></p>
  <p data-lake-id="u4b1de904" id="u4b1de904"><span data-lake-id="u1c360be0" id="u1c360be0">首先，使用一个子查询来获取限定条件下的一小部分主键id，这部分 id 对应于我们分页的目标区域。然后，使用这些 id 在主查询中获取完整的行数据。</span></p>
  <p data-lake-id="u85956f23" id="u85956f23"><br></p>
  <p data-lake-id="uf1c985a7" id="uf1c985a7"><span data-lake-id="u5dc0c9a2" id="u5dc0c9a2">以上SQL，在name有索引的情况下，子查询中查询id是不需要回表的。而当我们查询出我们想要的10个ID之后，基于ID查询不仅快，而且要查的数据量也很少。</span></p>
  <p data-lake-id="u53312d7a" id="u53312d7a"><br></p>
  <p data-lake-id="ube2c2667" id="ube2c2667"><br></p>
  <h3 data-lake-id="ywMp4" id="ywMp4"><span data-lake-id="ub32ab280" id="ub32ab280">使用子查询和ID过滤优化</span></h3>
  <p data-lake-id="u8df76e42" id="u8df76e42"><br></p>
  <p data-lake-id="ub83a18d0" id="ub83a18d0"><span data-lake-id="udc37c934" id="udc37c934">和上面的方法类似，我们还可以把SQL优化成：</span></p>
  <p data-lake-id="u71b3f044" id="u71b3f044"><span data-lake-id="u9e00815d" id="u9e00815d">​</span><br></p>
  <pre lang="java"><code>
SELECT c1, c2, cn...
FROM table
WHERE name = "Hollis"
  AND id &gt; (SELECT id FROM table WHERE name = "Hollis" ORDER BY id LIMIT 1000000, 1)
ORDER BY id
LIMIT 10

</code></pre>
  <p data-lake-id="ue9e63529" id="ue9e63529"><br></p>
  <p data-lake-id="u7785be24" id="u7785be24"><br></p>
  <p data-lake-id="uee157487" id="uee157487"><span data-lake-id="u1b052d53" id="u1b052d53">这个方法代替了join的方式，使用了一个子查询来获取从哪里开始分页的参考点，基于ID做范围查询。但是这个方案有个弊端，那就是要求ID一定要是自增的。</span></p>
  <p data-lake-id="u5a8b83e3" id="u5a8b83e3"><span data-lake-id="u696f07b9" id="u696f07b9">​</span><br></p>
  <p data-lake-id="uea83cd46" id="uea83cd46"><span data-lake-id="u08a5596f" id="u08a5596f">和上面的方案同理，他也可以减少回表的次数。</span></p>
  <p data-lake-id="ubefed178" id="ubefed178"><br></p>
  <h3 data-lake-id="ePakW" id="ePakW"><span data-lake-id="uaad2cd8a" id="uaad2cd8a">记录上一个ID</span></h3>
  <p data-lake-id="u007a17e9" id="u007a17e9"><span data-lake-id="u76e02707" id="u76e02707">​</span><br></p>
  <p data-lake-id="uba2a83a5" id="uba2a83a5"><span data-lake-id="ub9255c10" id="ub9255c10">还有一种方式，是上面这个方式的变种，就是如果能提前预估要查询的分页的条件的话，是可以很大程度提升性能的。比如记住上一页的最大ID，下一页查询的时候，就可以可以根据id &gt;　max_id_in_last_page 进行查询。</span></p>
  <p data-lake-id="u51bb3e83" id="u51bb3e83"><span data-lake-id="u64bef33f" id="u64bef33f">​</span><br></p>
  <p data-lake-id="ud238bd68" id="ud238bd68"><span data-lake-id="u2d2aa915" id="u2d2aa915">​</span><br></p>
  <h3 data-lake-id="fYmfw" id="fYmfw"><span data-lake-id="u930eb5e6" id="u930eb5e6">使用搜索引擎</span></h3>
  <p data-lake-id="uaa104da4" id="uaa104da4"><br></p>
  <p data-lake-id="u71134935" id="u71134935"><span data-lake-id="ued3ebfb9" id="ued3ebfb9">另外，如果是基于文本内容的搜索，可以使用 Elasticsearch 这样的全文搜索引擎来优化深度分页性能。但是需要注意的是，ES也会有深度分页的问题，只不过他的影响比MySQL要小一些。</span></p>
  <p data-lake-id="u4e7d7c5d" id="u4e7d7c5d"><span data-lake-id="ubf78ef14" id="ubf78ef14">​</span><br></p>
 </body>
</html>